#include <ne.h>
#include <system/GlutApplication.h>

int Width;
int Height;

int lineMode = 0;
int alphaMode = 0;
int lightOn = 0;
ne::Vector2 mousePosition;
bool mouseLeftOn = false;
bool mouseMiddleOn = false;
bool mouseRightOn = false;
ne::real ParallaxFactor = 0.1f;
ne::real const JointRadius = 1.0f;
ne::Engine *pEngine = 0;
ne::Light *light = 0;
ne::Timer timer;
ne::Camera *camera = 0;
ne::Viewport *viewport = 0;
ne::GlutApplication *pApplication = 0;
ne::ModelPtr pModel;
ne::MeshPtr pMesh;
ne::SkeletonPtr pSkeleton;
ne::AnimationPtr pAnimation;
ne::TexturePtr pTexture;
ne::TexturePtr pBumpTex;
ne::Timer animationTimer;
ne::GLSLProgramPtr pProgram;
/*
int tangentIndex = 0;
int blendWeightIndex = 0;
int blendJointIndex = 0;
*/
size_t vdSize = 0;

std::vector<ne::uint32_t> jointIndexList;

void keyboard(unsigned char key, int x, int y);
void keyboardUp(unsigned char key, int x, int y);
void mouseFunc(int button, int state, int x, int y);
void motionFunc(int x, int y);
void loop(void);
void redraw(void);
void drawFPS(void);
void resize(int width, int height);
void init();

int main(int argc, char *argv[]) /* I - Command-line arguments */
{
    pApplication = new ne::GlutApplication();//(argc, argv);
    glutReshapeFunc(resize);
    glutDisplayFunc(loop);
    glutKeyboardFunc(keyboard);
    glutMouseFunc(mouseFunc);
    glutMotionFunc(motionFunc);

    pEngine = pApplication->engine;
    if (!pEngine || !pEngine->isReady())
    {
        delete pApplication;
        return 0;
    }

    ne::ModelManager *pModelManager = pEngine->getModelManager();
    pModel = pModelManager->create("TestModel", "", true);

    // Create Mesh
    if (argc > 1)
    {
        ne::MeshManager *pMeshManager = pEngine->getMeshManager();
        pMesh = pMeshManager->create("TestMesh", argv[1]);
        pMesh->load();
        pTexture = pEngine->getTextureManager()->createTextureFromFile("logo", "../image/logo.png", ne::TF_RGBA);
        if (pTexture.notNull())
        {
            pTexture->load();
            pTexture->setNumMipmap(10);
            pTexture->setFilter(ne::TFF_LINEAR_MIPMAP_LINEAR, ne::TFF_LINEAR);
            pTexture->setLOD(0, 10, 0.0f);
            pTexture->createRenderTexture();
        }
        pBumpTex = pEngine->getTextureManager()->createTextureFromFile("logo_normal", "../image/logo_normal.png", ne::TF_RGBA);
        if (pBumpTex.notNull())
        {
            pBumpTex->load();
            pBumpTex->setNumMipmap(10);
            pBumpTex->setFilter(ne::TFF_LINEAR_MIPMAP_LINEAR, ne::TFF_LINEAR);
            pBumpTex->setLOD(0, 10, 0.0f);
            pBumpTex->createRenderTexture();
        }

        pModel->createSkin("TestSkin", pMesh);

        if (argc > 2)
        {
            ne::SkeletonManager *pSkeletonManager = pEngine->getSkeletonManager();
            pSkeleton = pSkeletonManager->create("TestSkeleton", argv[2]);
            pSkeleton->load();
            pModel->setSkeleton(pSkeleton);
            pSkeleton = pModel->getSkeleton();
        }

        if (argc > 3)
        {
            ne::AnimationManager *pAnimationManager = pEngine->getAnimationManager();
            ne::AnimationGroupPtr pAnimationGroup = pAnimationManager->create("TestAnimationGroup", argv[3]);
            pAnimation = pAnimationGroup->create("TestAnimation", argv[3]);
            pAnimation->load();
            pModel->setAnimationGroup(pAnimationGroup);
        }

        ne::MeshXMLLoader meshXMLLoader;
        ne::MeshPtr pXMLMesh;
        pXMLMesh = pMeshManager->create("CubeMesh", "../model/cube.xml", true, &meshXMLLoader);
        pXMLMesh->load();
        pXMLMesh->setFileName("../model/cube.mesh");
        pXMLMesh->download();
    }

    init();

    glutMainLoop();

    delete pApplication;

    return 0;
}

/*
void setLineMode(ne::UIButton *button)
{
    lineMode ^= 1;
    if (lineMode)
    {
        button->setText("Fill Mode");
    }
    else
    {
        button->setText("Line Mode");
    }
}

void setAlphaMode(ne::UIButton *button)
{
    alphaMode ^= 1;
    if (alphaMode)
    {
        button->setText("Color Mode");
    }
    else
    {
        button->setText("Alpha Mode");
    }
}

void setLightOn(ne::UIButton *button)
{
    lightOn ^= 1;
    if (lightOn)
    {
        button->setText("Light Off");
    }
    else
    {
        button->setText("Light On");
    }
}
*/

void init()
{
    camera = new ne::Camera();
    viewport = new ne::Viewport(camera);
    camera->setPosition(ne::Vector3::UNIT_Z * 100);

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClearDepth(1);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);

    glCullFace(GL_BACK);
    glEnable(GL_CULL_FACE);

    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
    glDisable(GL_LIGHTING);

    glShadeModel(GL_SMOOTH);
    glEnable(GL_POINT_SMOOTH);
    glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
    glEnable(GL_LINE_SMOOTH);
    glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    glEnable(GL_POLYGON_SMOOTH);
    glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);

    ne::GLSLManager *pManager = pEngine->getGLSLManager();
    pProgram = pManager->create("Bump", "../shader/bump.glpg", true);
    ne::GLSLShaderPtr pVShader;
    pVShader = pProgram->createShaderFromFile("BumpVertexShader", "../shader/bump.glvs", ne::SHADER_TYPE_VERTEX);
    //ne::GLSLShaderPtr pGShader;
    //pGShader = pProgram->createShaderFromFile("BumpVertexShader", "../shader/bump.glgs", ne::SHADER_TYPE_GEOMETRY);
    ne::GLSLShaderPtr pFShader;
    pFShader = pProgram->createShaderFromFile("BumpFragmentShader", "../shader/bump.glfs", ne::SHADER_TYPE_FRAGMENT);
    pProgram->attachShader(pVShader);
    //pProgram->attachShader(pGShader);
    pProgram->attachShader(pFShader);

    pProgram->setAttribLocation(uint32_t(ne::VAS_POSITION), "nePosition");
    pProgram->setAttribLocation(uint32_t(ne::VAS_NORMAL), "neNormal");
    pProgram->setAttribLocation(uint32_t(ne::VAS_COLOR), "neColor");
    pProgram->setAttribLocation(uint32_t(ne::VAS_TEXTURE_COORD), "neTexCoord");
    pProgram->setAttribLocation(uint32_t(ne::VAS_BLEND_WEIGHT), "neBlendWeight");
    pProgram->setAttribLocation(uint32_t(ne::VAS_BLEND_INDEX), "neBlendIndex");
    pProgram->setAttribLocation(uint32_t(ne::VAS_TANGENT), "neTangent");

    pProgram->link();
    pProgram->validate();

    /*
    tangentIndex = pProgram->getAttribLocation("neTangent");
    std::cout << "TangentLocation " << tangentIndex << std::endl;

    blendWeightIndex = pProgram->getAttribLocation("neBlendWeights");
    std::cout << "JointWeightLocation " << blendWeightIndex << std::endl;

    blendJointIndex = pProgram->getAttribLocation("neBlendJoints");
    std::cout << "JointIndexLocation " << blendJointIndex << std::endl;
    */

    // Set light
    light = new ne::Light();
    light->setType(ne::LT_POINT);
    light->setAmbient(ne::Color::GRAY);
    light->setAttenuationFactor(ne::LAT_LINEAR, 0.005f);
    light->setPosition(ne::Vector3(0.0f, 0.0f, 20.0f));

    animationTimer.reset();

    /*
    ne::UIComponent *pUI = pEngine->getUIManager()->createUIComponent("UIButton", "Line Mode Button");
    pUI->setPosition(ne::Vector3(0.0f, 0.0f, 0.0f));
    pUI->setSize(100.0f, 20.0f);
    ne::UIButton *pButton = dynamic_cast<ne::UIButton*>(pUI);
    pButton->setText("Line Mode");
    pButton->setCallbackFunc(setLineMode);
    pButton->setColor(ne::Color(0.0f, 0.6f, 1.0f, 1.0f));
    pButton->setTextColor(ne::Color(1.0f, 1.0f, 1.0f));

    pUI = pEngine->getUIManager()->createUIComponent("UIButton", "Alpha Mode Button");
    pUI->setPosition(ne::Vector3(0.0f, 20.0f, 0.0f));
    pUI->setSize(100.0f, 20.0f);
    pButton = dynamic_cast<ne::UIButton*>(pUI);
    pButton->setText("Alpha Mode");
    pButton->setCallbackFunc(setAlphaMode);
    pButton->setColor(ne::Color(0.0f, 0.6f, 1.0f, 1.0f));
    pButton->setTextColor(ne::Color(1.0f, 1.0f, 1.0f));

    pUI = pEngine->getUIManager()->createUIComponent("UIButton", "Light Button");
    pUI->setPosition(ne::Vector3(0.0f, 40.0f, 0.0f));
    pUI->setSize(100.0f, 20.0f);
    pButton = dynamic_cast<ne::UIButton*>(pUI);
    pButton->setText("Light Off");
    pButton->setCallbackFunc(setLightOn);
    pButton->setColor(ne::Color(0.0f, 0.6f, 1.0f, 1.0f));
    pButton->setTextColor(ne::Color(1.0f, 1.0f, 1.0f));
    */
}

void keyboard(unsigned char key, int x, int y)
{
    if (key == 'w' || key == 'W')
    {
        camera->translate(ne::Vector3::NEGATIVE_UNIT_Z, ne::TS_LOCAL);
    }
    else if (key == 's' || key == 'S')
    {
        camera->translate(ne::Vector3::UNIT_Z, ne::TS_LOCAL);
    }
    else if (key == 'a' || key == 'A')
    {
        camera->translate(ne::Vector3::NEGATIVE_UNIT_X, ne::TS_LOCAL);
    }
    else if (key == 'd' || key == 'D')
    {
        camera->translate(ne::Vector3::UNIT_X, ne::TS_LOCAL);
    }
    else if (key == 'o' || key == 'O')
    {
        camera->translate(ne::Vector3::NEGATIVE_UNIT_Y, ne::TS_PARENT);
    }
    else if (key == 'u' || key == 'U')
    {
        camera->translate(ne::Vector3::UNIT_Y, ne::TS_PARENT);
    }
    else if (key == 'q' || key == 'Q')
    {
        camera->yaw(ne::Math::PI / 36);
    }
    else if (key == 'e' || key == 'E')
    {
        camera->yaw(- ne::Math::PI / 36);
    }
    else if (key == 'i' || key == 'I')
    {
        camera->pitch(ne::Math::PI / 36);
    }
    else if (key == 'k' || key == 'K')
    {
        camera->pitch(- ne::Math::PI / 36);
    }
    else if (key == 'j' || key == 'J')
    {
        camera->roll(ne::Math::PI / 36);
    }
    else if (key == 'l' || key == 'L')
    {
        camera->roll(- ne::Math::PI / 36);
    }
    else if (key == 'n' || key == 'N')
    {
        lineMode ^= 1;
    }
    else if (key == 'p' || key == 'P')
    {
        pModel->playAnimation("TestAnimation", 0, false);
    }
    else if (key == 'z' || key == 'Z')
    {
        light->setPosition(0.5f * light->getPosition());
    }
    else if (key == 'c' || key == 'C')
    {
        light->setPosition(2.0f * light->getPosition());
    }
    else if (key == 0x1b)
    {
        exit(1);
    }
}

void keyboardUp(unsigned char key, int x, int y)
{
}

void mouseFunc(int button, int state, int x, int y)
{
    if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
    {
        mousePosition.x = x;
        mousePosition.y = y;
        mouseLeftOn = true;
    }
    else if (button == GLUT_LEFT_BUTTON && state == GLUT_UP)
    {
        mouseLeftOn = false;
    }

    if (button == GLUT_MIDDLE_BUTTON && state == GLUT_DOWN)
    {
        mousePosition.x = x;
        mousePosition.y = y;
        mouseMiddleOn = true;
    }
    else if (button == GLUT_MIDDLE_BUTTON && state == GLUT_UP)
    {
        mouseMiddleOn = false;
    }

    if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
    {
        mousePosition.x = x;
        mousePosition.y = y;
        mouseRightOn = true;
    }
    else if (button == GLUT_RIGHT_BUTTON && state == GLUT_UP)
    {
        mouseRightOn = false;
    }
}

void motionFunc(int x, int y)
{
    if (mouseLeftOn)
    {
        float dx = x - mousePosition.x;
        float dy = y - mousePosition.y;
        ne::Quaternion q(ne::Vector3::UNIT_Y, 0.01f * dx);
        light->setPosition(q * light->getPosition());
        light->rotate(q, ne::TS_WORLD);

        q.fromAxisAngle(light->getRight(), 0.01f * dy);
        light->setPosition(q * light->getPosition());
        light->rotate(q, ne::TS_WORLD);
    }

    if (mouseMiddleOn)
    {
        float dx = x - mousePosition.x;
        ParallaxFactor = ne::Math::Clamp(ParallaxFactor + 0.0002 * dx, 0.0, 0.1);
    }

    if (mouseRightOn)
    {
        float dx = x - mousePosition.x;
        float dy = y - mousePosition.y;
        ne::Quaternion q(ne::Vector3::UNIT_Y, -0.01f * dx);
        ne::Vector3 vOffset = camera->getPosition();
        camera->setPosition(q * vOffset);
        camera->rotate(q, ne::TS_WORLD);

        q.fromAxisAngle(camera->getLeft(), 0.01f * dy);
        vOffset = camera->getPosition();
        if ((vOffset.crossProduct(ne::Vector3::UNIT_Y)).dotProduct((q * vOffset).crossProduct(ne::Vector3::UNIT_Y)) > 0.0f)
        {
            camera->setPosition(q * vOffset);
            camera->rotate(q, ne::TS_WORLD);
        }
    }

    mousePosition.x = x;
    mousePosition.y = y;
}

void loop(void)
{
    pModel->tick(0.001f * animationTimer.getIntervalMilliseconds());

    redraw();
}

void redraw(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //glMatrixMode(GL_MODELVIEW);
    const ne::Matrix4 &viewMatrix = camera->getViewMatrix();
    //glLoadMatrixf(viewMatrix.ptr());
    pEngine->getRenderSystem()->setViewMatrix(viewMatrix);

    if (lightOn)
    {
        //glEnable(GL_LIGHTING);
    }

    if (lineMode)
    {
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    }
    else
    {
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }

    if (alphaMode)
    {
        pEngine->getRenderSystem()->setDepthWriteEnable(false);
        glColor4f(0.0f, 0.5f, 1.0f, 0.5f);
    }
    else
    {
        pEngine->getRenderSystem()->setDepthWriteEnable(true);
        glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
    }

    glPushMatrix();
        if (pMesh.notNull())
        {
            if (pTexture.notNull())
            {
                pTexture->createRenderTexture();
                glBindTexture(pTexture->getType(), pTexture->getSource());
                glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
                glEnable(pTexture->getType());
            }

            if (pBumpTex.notNull())
            {
                glActiveTexture(GL_TEXTURE1);
                pBumpTex->createRenderTexture();
                glBindTexture(pBumpTex->getType(), pBumpTex->getSource());
                glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

                pProgram->useProgram();
                if (pSkeleton.notNull() && pSkeleton->getNumJoints())
                {
                    ne::Matrix4Vector matrices;
                    pSkeleton->getAllJointsOffsetTransformMatrices(matrices);
                    pProgram->setUniformArray("neJointOffsetMatrices", matrices.size(), &matrices[0]);
                }
                else
                {
                    ne::Matrix4 matrices[128];
                    pProgram->setUniformArray("neJointOffsetMatrices", 128, &matrices[0]);
                }

                pProgram->setUniform("neModelMatrix", pModel->getTransformMatrix());
                pProgram->setUniform("neViewMatrix", camera->getViewMatrix());
                pProgram->setUniform("neViewProjectionMatrix", camera->getViewMatrix() * camera->getProjectionMatrix());
                pProgram->setUniform("neFactor", ParallaxFactor);
                pProgram->setUniform("lightPos", light->getPosition());
                pProgram->setUniform("lightDir", light->getDirection());
                pProgram->setUniform("cameraPos", camera->getPosition());
                pProgram->setUniform("oriMap", 0);
                pProgram->setUniform("bumpMap", 1);
                pProgram->validate();
            }

            vdSize = 0;
            ne::VertexDataPtr vertexData = pMesh->getVertexData();
            const ne::SubMeshMap &subMeshMap = pMesh->getChildrenMap();
            ne::SubMeshMapConstIterator it = subMeshMap.begin();
            while (it != subMeshMap.end())
            {
                ne::SubMeshPtr pSubMesh = it->second;
                ne::MaterialPtr pMaterial = pSubMesh->getMaterial();

                //pEngine->getRenderSystem()->setMaterial(pMaterial);

                vdSize += vertexData->getSize();

                ne::VertexAttributePtr va;

                /* Version 330 compatibility
                va = vertexData->getVertexArray(ne::VAS_POSITION);
                if (va.notNull())
                {
                    glBindBuffer(GL_ARRAY_BUFFER, va->getSource());
                    glVertexPointer(va->getTypeCount(), va->getGLDataType(), 0, va->getOffsetPointer());
                    glEnableClientState(GL_VERTEX_ARRAY);
                }

                va = vertexData->getVertexArray(ne::VAS_NORMAL);
                if (va.notNull())
                {
                    glBindBuffer(GL_ARRAY_BUFFER, va->getSource());
                    glNormalPointer(va->getGLDataType(), 0, va->getOffsetPointer());
                    glEnableClientState(GL_NORMAL_ARRAY);
                }

                va = vertexData->getVertexArray(ne::VAS_COLOR);
                if (va.notNull())
                {
                    glBindBuffer(GL_ARRAY_BUFFER, va->getSource());
                    glColorPointer(va->getTypeCount(), va->getGLDataType(), 0, va->getOffsetPointer());
                    glEnableClientState(GL_COLOR_ARRAY);
                }

                va = vertexData->getVertexArray(ne::VAS_TANGENT);
                if (va.notNull())
                {
                    glBindBuffer(GL_ARRAY_BUFFER, va->getSource());
                    glVertexAttribPointer(tangentIndex, va->getTypeCount(), va->getGLDataType(), GL_TRUE, 0, va->getOffsetPointer());
                    glEnableVertexAttribArray(tangentIndex);
                }

                va = vertexData->getVertexArray(ne::VAS_BLEND_WEIGHT);
                if (va.notNull())
                {
                    glBindBuffer(GL_ARRAY_BUFFER, va->getSource());
                    glVertexAttribPointer(blendWeightIndex, va->getTypeCount(), va->getGLDataType(), GL_FALSE, 0, va->getOffsetPointer());
                    glEnableVertexAttribArray(blendWeightIndex);
                }

                va = vertexData->getVertexArray(ne::VAS_BLEND_INDEX);
                if (va.notNull())
                {
                    glBindBuffer(GL_ARRAY_BUFFER, va->getSource());
                    glVertexAttribPointer(blendJointIndex, va->getTypeCount(), va->getGLDataType(), GL_FALSE, 0, va->getOffsetPointer());
                    glEnableVertexAttribArray(blendJointIndex);
                }

                va = vertexData->getVertexArray(ne::VAS_TEXTURE_COORD);
                if (va.notNull())
                {
                    glBindBuffer(GL_ARRAY_BUFFER, va->getSource());
                    glClientActiveTexture(GL_TEXTURE0);
                    glTexCoordPointer(va->getTypeCount(), va->getGLDataType(), 0, va->getOffsetPointer());
                    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
                }

                va = vertexData->getVertexArray(ne::VAS_INDEX);
                if (va.notNull())
                {
                    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, va->getSource());
                    glDrawElements(vertexData->getGeometryType(), va->getCount(), va->getGLDataType(), va->getOffsetPointer());
                }

                glDisableClientState(GL_VERTEX_ARRAY);
                glDisableClientState(GL_NORMAL_ARRAY);
                glDisableClientState(GL_COLOR_ARRAY);
                glDisableVertexAttribArray(tangentIndex);
                glDisableVertexAttribArray(blendWeightIndex);
                glDisableVertexAttribArray(blendJointIndex);
                glDisableClientState(GL_TEXTURE_COORD_ARRAY);
                */

                // Version 330 core
                va = vertexData->getAttribute(ne::VAS_POSITION);
                if (va.notNull())
                {
                    glBindBuffer(GL_ARRAY_BUFFER, va->getSource());
                    glVertexAttribPointer(ne::VAS_POSITION, va->getGLDataCount(), va->getGLDataType(), GL_FALSE, va->getStride(), va->getOffsetPointer());
                    glEnableVertexAttribArray(ne::VAS_POSITION);
                }

                va = vertexData->getAttribute(ne::VAS_NORMAL);
                if (va.notNull())
                {
                    glBindBuffer(GL_ARRAY_BUFFER, va->getSource());
                    glVertexAttribPointer(ne::VAS_NORMAL, va->getGLDataCount(), va->getGLDataType(), GL_TRUE, va->getStride(), va->getOffsetPointer());
                    glEnableVertexAttribArray(ne::VAS_NORMAL);
                }

                va = vertexData->getAttribute(ne::VAS_COLOR);
                if (va.notNull())
                {
                    glBindBuffer(GL_ARRAY_BUFFER, va->getSource());
                    glVertexAttribPointer(ne::VAS_COLOR, va->getGLDataCount(), va->getGLDataType(), GL_FALSE, va->getStride(), va->getOffsetPointer());
                    glEnableVertexAttribArray(ne::VAS_COLOR);
                }

                va = vertexData->getAttribute(ne::VAS_TANGENT);
                if (va.notNull())
                {
                    glBindBuffer(GL_ARRAY_BUFFER, va->getSource());
                    glVertexAttribPointer(ne::VAS_TANGENT, va->getGLDataCount(), va->getGLDataType(), GL_TRUE, va->getStride(), va->getOffsetPointer());
                    glEnableVertexAttribArray(ne::VAS_TANGENT);
                }

                va = vertexData->getAttribute(ne::VAS_BLEND_WEIGHT);
                if (va.notNull())
                {
                    glBindBuffer(GL_ARRAY_BUFFER, va->getSource());
                    glVertexAttribPointer(ne::VAS_BLEND_WEIGHT, va->getGLDataCount(), va->getGLDataType(), GL_FALSE, va->getStride(), va->getOffsetPointer());
                    glEnableVertexAttribArray(ne::VAS_BLEND_WEIGHT);
                }

                va = vertexData->getAttribute(ne::VAS_BLEND_INDEX);
                if (va.notNull())
                {
                    glBindBuffer(GL_ARRAY_BUFFER, va->getSource());
                    glVertexAttribPointer(ne::VAS_BLEND_INDEX, va->getGLDataCount(), va->getGLDataType(), GL_FALSE, va->getStride(), va->getOffsetPointer());
                    glEnableVertexAttribArray(ne::VAS_BLEND_INDEX);
                }

                va = vertexData->getAttribute(ne::VAS_TEXTURE_COORD);
                if (va.notNull())
                {
                    glBindBuffer(GL_ARRAY_BUFFER, va->getSource());
                    glVertexAttribPointer(ne::VAS_TEXTURE_COORD, va->getGLDataCount(), va->getGLDataType(), GL_FALSE, va->getStride(), va->getOffsetPointer());
                    glEnableVertexAttribArray(ne::VAS_TEXTURE_COORD);
                }

                ne::IndexDataPtr indexData = pSubMesh->getIndexData();
                if (indexData.notNull())
                {
                    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexData->getSource());
                    glDrawElements(indexData->getPrimitiveType(), indexData->getCount(), indexData->getGLDataType(), indexData->getOffsetPointer());
                }

                glDisableVertexAttribArray(ne::VAS_POSITION);
                glDisableVertexAttribArray(ne::VAS_NORMAL);
                glDisableVertexAttribArray(ne::VAS_COLOR);
                glDisableVertexAttribArray(ne::VAS_TANGENT);
                glDisableVertexAttribArray(ne::VAS_BLEND_WEIGHT);
                glDisableVertexAttribArray(ne::VAS_BLEND_INDEX);
                glDisableVertexAttribArray(ne::VAS_TEXTURE_COORD);

                glBindBuffer(GL_ARRAY_BUFFER, 0);
                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

                ++it;
            }

            if (pTexture.notNull())
            {
                pProgram->useProgram(false);
                glDisable(pTexture->getType());
                glActiveTexture(GL_TEXTURE0);
            }
        }

    glPopMatrix();

    glDisable(GL_COLOR_SUM);

    glPushMatrix();
        if (pSkeleton.notNull())
        {
            const ne::JointVector &joints = pSkeleton->getJointVector();
            for (size_t i=0; i<joints.size(); ++i)
            {
                ne::Joint *pJoint = joints[i];
                glPushMatrix();
                    glColor4f(0.0f, 0.8f, 1.0f, 1.0f);
                    glMultMatrixf(pJoint->getTransformMatrix().ptr());
                    glutWireSphere(JointRadius, 8, 4);
                glPopMatrix();

                ne::Joint *pParent = dynamic_cast<ne::Joint*>(pJoint->getParent());
                if (pParent)
                {
                    glPushMatrix();
                        glMultMatrixf(pParent->getTransformMatrix().ptr());
                        ne::Vector3 line = pJoint->getPosition(ne::TS_PARENT);
                        ne::real length = line.length();
                        ne::Vector3 endPoint = ((length - JointRadius) / length) * line;
                        ne::Quaternion q = ne::Quaternion::MakeRotation(ne::Vector3::UNIT_Z, line);
                        ne::Vector3 pl = q * ne::Vector3(-JointRadius, 0.0f, JointRadius);
                        ne::Vector3 pr = q * ne::Vector3(JointRadius, 0.0f, JointRadius);
                        ne::Vector3 pu = q * ne::Vector3(0.0f, JointRadius, JointRadius);
                        ne::Vector3 pd = q * ne::Vector3(0.0f, -JointRadius, JointRadius);
                        glColor4f(1.0f, 0.5f, 0.0f, 1.0f);
                        glBegin(GL_LINES);
                            glVertex3fv(pl.ptr());
                            glVertex3fv(endPoint.ptr());

                            glVertex3fv(pd.ptr());
                            glVertex3fv(endPoint.ptr());

                            glVertex3fv(pr.ptr());
                            glVertex3fv(endPoint.ptr());

                            glVertex3fv(pu.ptr());
                            glVertex3fv(endPoint.ptr());

                            glVertex3fv(pl.ptr());
                            glVertex3fv(pu.ptr());

                            glVertex3fv(pu.ptr());
                            glVertex3fv(pr.ptr());

                            glVertex3fv(pr.ptr());
                            glVertex3fv(pd.ptr());

                            glVertex3fv(pd.ptr());
                            glVertex3fv(pl.ptr());
                        glEnd();
                   glPopMatrix();
                }
            }
        }
    glPopMatrix();

    glPushMatrix();
        const ne::AABB &aabb = pModel->getAABB();
        if (aabb.isFinite())
        {
            const ne::Vector3 *corners = aabb.getCorners();
            if (corners)
            {
                glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
                glBegin(GL_LINES);
                    glVertex3fv(corners[0].ptr());
                    glVertex3fv(corners[1].ptr());
                    glVertex3fv(corners[1].ptr());
                    glVertex3fv(corners[2].ptr());
                    glVertex3fv(corners[2].ptr());
                    glVertex3fv(corners[3].ptr());
                    glVertex3fv(corners[3].ptr());
                    glVertex3fv(corners[0].ptr());

                    glVertex3fv(corners[4].ptr());
                    glVertex3fv(corners[5].ptr());
                    glVertex3fv(corners[5].ptr());
                    glVertex3fv(corners[6].ptr());
                    glVertex3fv(corners[6].ptr());
                    glVertex3fv(corners[7].ptr());
                    glVertex3fv(corners[7].ptr());
                    glVertex3fv(corners[4].ptr());

                    glVertex3fv(corners[0].ptr());
                    glVertex3fv(corners[6].ptr());
                    glVertex3fv(corners[1].ptr());
                    glVertex3fv(corners[5].ptr());
                    glVertex3fv(corners[2].ptr());
                    glVertex3fv(corners[4].ptr());
                    glVertex3fv(corners[3].ptr());
                    glVertex3fv(corners[7].ptr());
                glEnd();
            }
        }
    glPopMatrix();

    glPushMatrix();
        glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
        ne::Vector3 pos = light->getPosition();
        glTranslatef(pos.x, pos.y, pos.z);
        glutSolidSphere(1.0f, 20, 20);
    glPopMatrix();

    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    drawFPS();

    glutSwapBuffers();
    glutPostRedisplay();
}

void resize(int width, int height)
{
    /* Save the new width and height */
    Width  = width;
    Height = height;

    /* Reset the viewport->.. */
    glViewport(0, 0, width, height);
    viewport->setPosition(0, 0);
    viewport->resize(width, height);

    const ne::Matrix4 &projectionMatrix = camera->getProjectionMatrix();
    pEngine->getRenderSystem()->setProjectionMatrix(projectionMatrix);
}

void drawFPS()
{
    glClear(GL_DEPTH_BUFFER_BIT);

    //Set matrices for ortho
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0.0f, viewport->getWidth(),
            0.0f, viewport->getHeight(),
            1.0f, -1.0f);

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();


    //Print text
    ne::FontPtr font = pEngine->getFont();
    ne::Vector3 pos(Width - 200.0f, 10.0f, 0.0f);
    float w = font->getFontWidth();
    float h = font->getFontHeight();

    char debugString[32];
    ne::real interval = timer.getIntervalMilliseconds();
	sprintf(debugString, "FPS : %.2f", 1000.0f / interval);

    font->renderText2D(debugString, pos, 32.0f*w, h, ne::Color(0.8f, 1.0f, 0.0f, 1.0f));

    pos.y += 12.0f;
    sprintf(debugString, "VD Size : %.2f MB", float(vdSize) / 1024.0f / 1024.0f);
    font->renderText2D(debugString, pos, 32.0f*w, h, ne::Color(0.0f, 0.8f, 1.0f, 1.0f));

    //pEngine->getUIManager()->render();

    //reset matrices
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

